﻿using Abp.Authorization;
using Abp.Configuration;
using Abp.Extensions;
using Abp.Runtime.Security;
using Abp.Runtime.Session;
using Abp.UI;
using Abp.Web.Configuration;
using Abp.Web.Models.AbpUserConfiguration;
using Abp.Zero.Configuration;
using ARchGL.Platform.Authorization.Accounts.Dto;
using ARchGL.Platform.Authorization.Impersonation;
using ARchGL.Platform.Authorization.Roles;
using ARchGL.Platform.Authorization.Users;
using ARchGL.Platform.Configuration;
using ARchGL.Platform.Debugging;
using ARchGL.Platform.MultiTenancy;
using ARchGL.Platform.Security.Recaptcha;
using ARchGL.Platform.Url;
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;

namespace ARchGL.Platform.Authorization.Accounts
{
    public class AccountAppService : PlatformAppServiceBase, IAccountAppService
    {
        public IAppUrlService AppUrlService { get; set; }

        public IRecaptchaValidator RecaptchaValidator { get; set; }

        private readonly IUserEmailer _userEmailer;
        private readonly AbpUserConfigurationBuilder _abpUserConfigurationBuilder;
        private readonly UserRegistrationManager _userRegistrationManager;
        private readonly IImpersonationManager _impersonationManager;
        private readonly IUserLinkManager _userLinkManager;
        private readonly IPasswordHasher<User> _passwordHasher;
        private readonly IWebUrlService _webUrlService;

        public AccountAppService(
            IUserEmailer userEmailer,
            AbpUserConfigurationBuilder abpUserConfigurationBuilder,
            UserRegistrationManager userRegistrationManager,
            IImpersonationManager impersonationManager,
            IUserLinkManager userLinkManager,
            IPasswordHasher<User> passwordHasher,
            IWebUrlService webUrlService)
        {
            _userEmailer = userEmailer;
            _abpUserConfigurationBuilder = abpUserConfigurationBuilder;
            _userRegistrationManager = userRegistrationManager;
            _impersonationManager = impersonationManager;
            _userLinkManager = userLinkManager;
            _passwordHasher = passwordHasher;
            _webUrlService = webUrlService;

            AppUrlService = NullAppUrlService.Instance;
            RecaptchaValidator = NullRecaptchaValidator.Instance;
        }

        /// <summary>
        /// 加密字符串
        /// </summary>
        /// <param name="conn"></param>
        /// <returns></returns>
        public string Encrypt(string conn)
        {
            return SimpleStringCipher.Instance.Encrypt(conn);
        }

        /// <summary>
        /// 解密字符串
        /// </summary>
        /// <param name="conn"></param>
        /// <returns></returns>
        public string Decrypt(string conn)
        {
            return SimpleStringCipher.Instance.Decrypt(conn);
        }

        /// <summary>
        /// 10位时间戳与时间 相互转换，各种语言间相互转换请参见：http://www.matools.com/timestamp
        /// </summary>
        /// <param name="str">日期或时间戳</param>
        /// <returns></returns>
        public string DateTimeConvert(string str)
        {
            return int.TryParse(str.Trim(), out int val)
                ? val.ToLocalDateTime().ToString()
                : DateTime.Parse(str.Trim()).ToUnixTimestamp().ToString();
        }

        /// <summary>
        /// 租户是否可用（可在页面打开时调用一次）
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task<IsTenantAvailableOutput> IsTenantAvailable(IsTenantAvailableInput input)
        {
            var tenant = await TenantManager.FindByTenancyNameAsync(input.TenancyName);
            if (tenant == null)
                return new IsTenantAvailableOutput(TenantAvailabilityState.NotFound);

            if (!tenant.IsActive)
                return new IsTenantAvailableOutput(TenantAvailabilityState.InActive);

            return new IsTenantAvailableOutput(TenantAvailabilityState.Available, tenant.Id, _webUrlService.GetServerRootAddress(input.TenancyName));
        }

        /// <summary>
        /// 获取用户配置信息
        /// </summary>
        /// <returns></returns>
        public async Task<AbpUserConfigurationDto> GetAll()
        {
            var userConfig = await _abpUserConfigurationBuilder.GetAll();
            return userConfig;
        }

        /// <summary>
        /// 获取权限点相关（所有和被授予的权限点）
        /// </summary>
        /// <returns></returns>
        public async Task<AbpUserAuthConfigDto> GetAllPermissions()
        {
            var config = new AbpUserAuthConfigDto();
            var allPermissionNames = PermissionManager.GetAllPermissions(false).Select(p => p.Name).ToList();
            var grantedPermissionNames = new List<string>();

            if (AbpSession.UserId.HasValue)
            {
                foreach (var permissionName in allPermissionNames)
                {
                    if (await PermissionChecker.IsGrantedAsync(permissionName))
                    {
                        grantedPermissionNames.Add(permissionName);
                    }
                }
            }

            config.AllPermissions = allPermissionNames.ToDictionary(permissionName => permissionName, permissionName => "true");
            config.GrantedPermissions = grantedPermissionNames.ToDictionary(permissionName => permissionName, permissionName => "true");

            return config;
        }

        /// <summary>
        /// 解析并获取租户TenantId
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public Task<int?> ResolveTenantId(ResolveTenantIdInput input)
        {
            if (string.IsNullOrEmpty(input.c))
            {
                return Task.FromResult(AbpSession.TenantId);
            }

            var parameters = SimpleStringCipher.Instance.Decrypt(input.c);
            var query = HttpUtility.ParseQueryString(parameters);

            if (query["tenantId"] == null)
            {
                return Task.FromResult<int?>(null);
            }

            var tenantId = Convert.ToInt32(query["tenantId"]) as int?;
            return Task.FromResult(tenantId);
        }

        /// <summary>
        /// 注册
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task<RegisterOutput> Register(RegisterInput input)
        {
            if (UseCaptchaOnRegistration())
            {
                await RecaptchaValidator.ValidateAsync(input.CaptchaResponse);
            }

            if (input.Password.Length < 32) input.Password = input.Password.ToMd5();

            var user = await _userRegistrationManager.RegisterAsync(
                input.Name,
                input.Surname,
                input.EmailAddress,
                input.UserName,
                input.Password,
                false,
                AppUrlService.CreateEmailActivationUrlFormat(AbpSession.TenantId)
            );

            var isEmailConfirmationRequiredForLogin = await SettingManager.GetSettingValueAsync<bool>(AbpZeroSettingNames.UserManagement.IsEmailConfirmationRequiredForLogin);

            return new RegisterOutput
            {
                CanLogin = user.IsActive && (user.IsEmailConfirmed || !isEmailConfirmationRequiredForLogin)
            };
        }

        /// <summary>
        /// 发送重置密码验证码
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task SendPasswordResetCode(SendPasswordResetCodeInput input)
        {
            var user = await GetUserByChecking(input.EmailAddress);
            user.SetNewPasswordResetCode();
            await _userEmailer.SendPasswordResetLinkAsync(
                user,
                AppUrlService.CreatePasswordResetUrlFormat(AbpSession.TenantId)
                );
        }

        /// <summary>
        /// 重置密码
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task<ResetPasswordOutput> ResetPassword(ResetPasswordInput input)
        {
            var user = await UserManager.GetUserByIdAsync(input.UserId);
            if (user == null || user.PasswordResetCode.IsNullOrEmpty() || user.PasswordResetCode != input.ResetCode)
            {
                throw new UserFriendlyException(L("InvalidPasswordResetCode"), L("InvalidPasswordResetCode_Detail"));
            }

            user.CheckType(); //接口租户不能被重置密码，如果需要可以手动调试获取密码再修改数据库

            if (input.Password.Length < 32) input.Password = input.Password.ToMd5();

            await UserManager.InitializeOptionsAsync(AbpSession.TenantId);
            CheckErrors(await UserManager.ChangePasswordAsync(user, input.Password));
            user.PasswordResetCode = null;
            user.IsEmailConfirmed = true;
            user.ShouldChangePasswordOnNextLogin = false;

            await UserManager.UpdateAsync(user);

            return new ResetPasswordOutput
            {
                CanLogin = user.IsActive,
                UserName = user.UserName
            };
        }

        /// <summary>
        /// 系统管理员专用重置密码
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        [AbpAuthorize]
        public async Task ResetPasswordByAdmin(long userId)
        {
            var user = await UserManager.GetUserByIdAsync(userId);
            if (user == null) throw new UserFriendlyException("用户不存在，请重新输入");

            var roles = await UserManager.GetRolesAsync(UserManager.GetUser());
            if (roles == null || !roles.Contains(StaticRoleNames.Tenants.Admin))
                throw new UserFriendlyException("当前用户没有重置密码权限，请联系管理员");

            //if (!CheckInRole(StaticRoleNames.Tenants.Admin))
            //    throw new UserFriendlyException("当前用户没有重置密码权限，请联系管理员");

            await UserManager.InitializeOptionsAsync(AbpSession.TenantId);
            await UserManager.ChangePasswordAsync(user, user.UserName.ToMd5());
            await UserManager.UpdateAsync(user);
        }

        /// <summary>
        /// 首次登录时修改密码
        /// </summary>
        /// <returns></returns>
        public async Task ChangePasswordOnFirstLogin(ChangePasswordOnFirstLoginInput input)
        {
            var user = await UserManager.GetUserByUserNameAsync(input.UserName);
            if (user == null) throw new UserFriendlyException("用户不存在，请重新输入");

            if (!user.Password.IsNullOrEmpty() || !user.ShouldChangePasswordOnNextLogin)
                throw new UserFriendlyException(L("YourRequestIsInvalid")); //只有无密码且需要登录修改密码用户才能调用该接口

            if (user.EmailAddress.IsNullOrEmpty())//临时解决未填写邮箱时，出现邮箱被占用异常
                user.EmailAddress = GuidGenerator.Create() + "@TEMP.com";

            if (input.Password.Length < 32) input.Password = input.Password.ToMd5();

            user.ShouldChangePasswordOnNextLogin = false;
            await UserManager.InitializeOptionsAsync(AbpSession.TenantId);
            await UserManager.ChangePasswordAsync(user, input.Password);
            await UserManager.UpdateAsync(user);
        }

        /// <summary>
        /// 发送激活地址
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task SendEmailActivationLink(SendEmailActivationLinkInput input)
        {
            var user = await GetUserByChecking(input.EmailAddress);
            user.SetNewEmailConfirmationCode();
            await _userEmailer.SendEmailActivationLinkAsync(
                user,
                AppUrlService.CreateEmailActivationUrlFormat(AbpSession.TenantId)
            );
        }

        /// <summary>
        /// 激活邮箱
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task ActivateEmail(ActivateEmailInput input)
        {
            var user = await UserManager.GetUserByIdAsync(input.UserId);
            if (user == null || user.EmailConfirmationCode.IsNullOrEmpty() || user.EmailConfirmationCode != input.ConfirmationCode)
            {
                throw new UserFriendlyException(L("InvalidEmailConfirmationCode"), L("InvalidEmailConfirmationCode_Detail"));
            }

            user.IsEmailConfirmed = true;
            user.EmailConfirmationCode = null;

            await UserManager.UpdateAsync(user);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        [AbpAuthorize(AppPermissions.Pages_Administration_Users_Impersonation)]
        public virtual async Task<ImpersonateOutput> Impersonate(ImpersonateInput input)
        {
            return new ImpersonateOutput
            {
                ImpersonationToken = await _impersonationManager.GetImpersonationToken(input.UserId, input.TenantId),
                TenancyName = await GetTenancyNameOrNullAsync(input.TenantId)
            };
        }

        public virtual async Task<ImpersonateOutput> BackToImpersonator()
        {
            return new ImpersonateOutput
            {
                ImpersonationToken = await _impersonationManager.GetBackToImpersonatorToken(),
                TenancyName = await GetTenancyNameOrNullAsync(AbpSession.ImpersonatorTenantId)
            };
        }

        public virtual async Task<SwitchToLinkedAccountOutput> SwitchToLinkedAccount(SwitchToLinkedAccountInput input)
        {
            if (!await _userLinkManager.AreUsersLinked(AbpSession.ToUserIdentifier(), input.ToUserIdentifier()))
            {
                throw new Exception(L("This account is not linked to your account"));
            }

            return new SwitchToLinkedAccountOutput
            {
                SwitchAccountToken = await _userLinkManager.GetAccountSwitchToken(input.TargetUserId, input.TargetTenantId),
                TenancyName = await GetTenancyNameOrNullAsync(input.TargetTenantId)
            };
        }

        #region Private method

        private bool UseCaptchaOnRegistration()
        {
            if (DebugHelper.IsDebug)
            {
                return false;
            }

            return SettingManager.GetSettingValue<bool>(AppSettings.UserManagement.UseCaptchaOnRegistration);
        }

        private async Task<Tenant> GetActiveTenantAsync(int tenantId)
        {
            var tenant = await TenantManager.FindByIdAsync(tenantId);
            if (tenant == null)
            {
                throw new UserFriendlyException(L("UnknownTenantId{0}", tenantId));
            }

            if (!tenant.IsActive)
            {
                throw new UserFriendlyException(L("TenantIdIsNotActive{0}", tenantId));
            }

            return tenant;
        }

        private async Task<string> GetTenancyNameOrNullAsync(int? tenantId)
        {
            return tenantId.HasValue ? (await GetActiveTenantAsync(tenantId.Value)).TenancyName : null;
        }

        private async Task<User> GetUserByChecking(string inputEmailAddress)
        {
            var user = await UserManager.FindByEmailAsync(inputEmailAddress);
            if (user == null)
            {
                throw new UserFriendlyException(L("InvalidEmailAddress"));
            }

            return user;
        }

        #endregion
    }
}